/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package main;

import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import object.Polygon;
import java.util.List;
import javax.media.opengl.GL;
import object.CS455Matrix;
import object.Color;
import object.Point;
import object.Shape;
import object.Sphere;
import object.Zbuffer;

/**
 *
 * @author dheath
 */
public class CS455
{

    private List<Shape> polygons;
    private Zbuffer zbuffer;
    private Point center;
    private float transY = 0;
    private float transX = 0;
    private float transZ = 0;
    private float rotX = 0;
    private float rotY = 0;
    private float rotZ = 0;
    private float scale = 1;
    private boolean perspective = true;
    private boolean shading = true;
    
    //Default view (1)
    private Point lookAt = new Point(0, 0, 0, 1);
    private Point lookFrom = new Point(0, 0, 100, 1);
    private Point viewUp = new Point(0, 1, 0, 1);
    private float fieldOfView = (float) Math.PI / 3;
    
    
                
    public CS455()
    {
        zbuffer = new Zbuffer();
        center = new Point(0, 0, 0, 1);
        
    }

    public void KeyStroke(KeyEvent e)
    {
        switch (e.getKeyCode())
        {
            case KeyEvent.VK_1:
                //Default
                lookAt = new Point(0, 0, 0, 1);
                lookFrom = new Point(0, 0, 100, 1);

                viewUp = new Point(0, 1, 0, 1);

                fieldOfView = (float) Math.PI / 3;
                break;
            case KeyEvent.VK_2:
                //from x
                lookFrom = new Point(-100, 0, 0, 1);

                lookAt = new Point(0, 0, 0, 1);
                viewUp = new Point(0, 1, 0, 1);
                fieldOfView = (float) Math.PI / 3;
                break;
            case KeyEvent.VK_3:
                //from y
                lookFrom = new Point(0, 100, 0, 1);

                lookAt = new Point(0, 0, 0, 1);
                viewUp = new Point(1, 0, 0, 1);
                fieldOfView = (float) Math.PI / 3;
                break;
            case KeyEvent.VK_4:
                //sideways
                lookFrom = new Point(0, 0, 100, 1);
                viewUp = new Point(1, 1, 0, 1);

                lookAt = new Point(0, 0, 0, 1);
                fieldOfView = (float) Math.PI / 3;
                break;
            case KeyEvent.VK_5:
                //wide view
                lookFrom = new Point(0, 0, 100, 1);
                fieldOfView = (float) Math.PI / 2;

                viewUp = new Point(0, 1, 0, 1);
                lookAt = new Point(0, 0, 0, 1);
                break;
            case KeyEvent.VK_6:
                //look at higher
                lookFrom = new Point(0, 0, 100, 1);
                lookAt = new Point(0, 20, 0, 1);

                viewUp = new Point(0, 1, 0, 1);

                fieldOfView = (float) Math.PI / 3;
                break;
            case KeyEvent.VK_7:
                //look from other side
                lookFrom = new Point(0, 0, -100, 1);
                lookAt = new Point(0, 0, 0, 1);

                viewUp = new Point(0, 1, 0, 1);

                fieldOfView = (float) Math.PI / 3;
                break;
            case KeyEvent.VK_UP:
                transY += 2;
                break;
            case KeyEvent.VK_DOWN:
                transY -= 2;
                break;
            case KeyEvent.VK_LEFT:
                transX -= 2;
                break;
            case KeyEvent.VK_RIGHT:
                transX += 2;
                break;
            case KeyEvent.VK_INSERT:
                transZ += 2;
                break;
            case KeyEvent.VK_DELETE:
                transZ -= 2;
                break;
            case KeyEvent.VK_PAGE_UP:
                scale += .1;
                break;
            case KeyEvent.VK_PAGE_DOWN:
                scale -= .1;
                break;

            case KeyEvent.VK_Q:
                rotX += .1;
                break;
            case KeyEvent.VK_A:
                rotX -= .1;
                break;
            case KeyEvent.VK_W:
                rotY += .1;
                break;
            case KeyEvent.VK_S:
                rotY -= .1;
                break;
            case KeyEvent.VK_E:
                rotZ += .1;
                break;
            case KeyEvent.VK_D:
                rotZ -= .1;
                break;
            case KeyEvent.VK_P:
                perspective = !perspective;
                break;
            case KeyEvent.VK_O:
                shading = !shading;
                break;
        }
    }

    public void transform()
    {

        double[][] origin = {{1, 0, 0, 0 - center.getX()},
            {0, 1, 0, 0 - center.getY()},
            {0, 0, 1, 0 - center.getZ()},
            {0, 0, 0, 1}};

        //Y
        double[][] rot = {{Math.cos(rotY), 0, Math.sin(rotY), 0},
            {0, 1, 0, 0},
            {-1 * Math.sin(rotY), 0, Math.cos(rotY), 0},
            {0, 0, 0, 1}};

        //X
        double[][] rot2 = {{1, 0, 0, 0},
            {0, Math.cos(rotX), -1 * Math.sin(rotX), 0},
            {0, Math.sin(rotX), Math.cos(rotX), 0},
            {0, 0, 0, 1}};

        //Z
        double[][] rot3 = {{Math.cos(rotZ), -1 * Math.sin(rotZ), 0, 0},
            {Math.sin(rotZ), Math.cos(rotZ), 0, 0},
            {0, 0, 1, 0},
            {0, 0, 0, 1}};

        double[][] sca = {{scale, 0, 0, 0},
            {0, scale, 0, 0},
            {0, 0, scale, 0},
            {0, 0, 0, 1}};



        double[][] back = {{1, 0, 0, center.getX()},
            {0, 1, 0, center.getY()},
            {0, 0, 1, center.getZ()},
            {0, 0, 0, 1}};



        double[][] tranY = {{1, 0, 0, transX},
            {0, 1, 0, transY},
            {0, 0, 1, transZ},
            {0, 0, 0, 1}};


        CS455Matrix transYm = new CS455Matrix(tranY);

        CS455Matrix trans = new CS455Matrix(origin);

        CS455Matrix scalem = new CS455Matrix(sca);

        CS455Matrix rotate = new CS455Matrix(rot);
        CS455Matrix rotate2 = new CS455Matrix(rot2);
        CS455Matrix rotate3 = new CS455Matrix(rot3);

        CS455Matrix tBack = new CS455Matrix(back);


        //CS455Matrix M = transYm.times(trans.times(rotate3.times(rotate2.times(rotate.times(scalem.times(tBack))))));
        CS455Matrix M = tBack.times(scalem.times(rotate.times(rotate2.times(rotate3.times(trans.times(transYm))))));

        //System.out.println(M.toString());

        center = M.multiplyPoint(center);
        for (Shape poly : polygons)
        {
            if(poly instanceof Polygon)
            {
                continue;
            }
                
            poly.multiplyMatrix(M);
            if(poly instanceof Sphere)
            {
                ((Sphere)poly).setRadius(((Sphere)poly).getRadius()*scale);
            }
        }

        transY = 0;
        transX = 0;
        transZ = 0;

        scale = 1;
    }

    private CS455Matrix perspective(float d)
    {
        double[][] pers = {{1, 0, 0, 0},
            {0, 1, 0, 0},
            {0, 0, 1, 0},
            {0, 0, (-1.0 / d), 1}};

        double[][] par = {{1, 0, 0, 0},
            {0, 1, 0, 0},
            {0, 0, 1, 0},
            {0, 0, 0, 1}};

        CS455Matrix per;

        
        if (perspective)
        {
            per = new CS455Matrix(par);
        } else
        {
            per = new CS455Matrix(par);
        }

        return per;
    }

    public CS455Matrix getCameraMatrix()
    {
        //N = lookfrom - lookat
        Point N = lookFrom.minus(lookAt);
        Point U = viewUp.cross(N);
        Point V = N.cross(U);


        N.normalize();
        U.normalize();
        V.normalize();
        //Normalize

        //translate m
        //change of basis

        double[][] trans = {{1, 0, 0, 0 - lookAt.getX()},
            {0, 1, 0, 0 - lookAt.getY()},
            {0, 0, 1, 0 - lookAt.getZ()},
            {0, 0, 0, 1}};

        CS455Matrix per = new CS455Matrix(trans);

        double[][] bas = {{U.getX(), U.getY(), U.getZ(), 0},
            {V.getX(), V.getY(), V.getZ(), 0},
            {N.getX(), N.getY(), N.getZ(), 0},
            {0, 0, 0, 1}};

        CS455Matrix basis = new CS455Matrix(bas);


        return basis.times(per);
    }

    public CS455Matrix scaleWorld(float low, float high)
    {
        float scalef = 100 / (high - low);

        double[][] view = {{scalef, 0, 0, 0},
            {0, scalef, 0, 0},
            {0, 0, scalef, 0},
            {0, 0, 0, 1}};

        return new CS455Matrix(view);
    }

    
    
    public CS455Matrix viewPort(float dist)
    {
        float Xmax = (float) Math.tan(fieldOfView / 2) * dist;

        float scalef = 800 / (Xmax * 2);

        double[][] trans = {{1, 0, 0, Xmax},
            {0, 1, 0, Xmax},
            {0, 0, 1, 0},
            {0, 0, 0, 1}};

        double[][] view = {{scalef, 0, 0, 0},
            {0, scalef, 0, 0},
            {0, 0, 1, 0},
            {0, 0, 0, 1}};

        CS455Matrix t = new CS455Matrix(trans);
        CS455Matrix v = new CS455Matrix(view);

        return v.times(t);
    // return normalizePoints(topRight.getX(), bottomLeft.getY(), newPolys);
    }

    public void drawPolygons(GL gl)
    {
        float dist = lookAt.distance(lookFrom);

        CS455Matrix cam = getCameraMatrix();


        CS455Matrix per = perspective(dist);
        CS455Matrix view = viewPort(dist);

        //zbuffer.clearZbuffer();

       // List<Polygon> newPolys = new ArrayList();

        CS455Matrix m = view.times(per.times(cam));
        
        
        RayTracing ray = new RayTracing();
        
        ray.rayTrace(gl,polygons, m,lookFrom);
        
        
        
        /*for (Polygon poly : polygons)
        {
            Polygon pp = new Polygon();
            for (Point p : poly.getPoints())
            {
                //System.out.println("B: "+p);
                Point newP = m.multiplyPoint(p);

                // Point n = m.multiplyPoint(p.getNormal());

                newP.setX((int) (newP.getX() / newP.getW()));
                newP.setY((int) (newP.getY() / newP.getW()));
                //newP.setZ(newP.getZ()/newP.getW());
                newP.setNormal(p.getNormal());
                pp.addPointToPolygon(newP);
            }   
            pp.setColor(poly.getColor());
            newPolys.add(pp);
        }
        
        
        for (int i = 0; i < newPolys.size(); i++)
        {
            //if(i > polygonsDraw.size()-500 && i < polygonsDraw.size() -210)
            //{
            Polygon poly = newPolys.get(i);
            Polygon world = polygons.get(i);
            poly.draw(gl, zbuffer, lookFrom,world,shading);
        //}
        }*/
    }
    
    public void buildWorld()
    {
        polygons = new ArrayList();
        
        Sphere sph = new Sphere(new Point(0,0,0,1),20);
        sph.setColor(new Color(1f,1f,0f));
        polygons.add(sph);
        
        sph = new Sphere(new Point(30,30,0,1),5);
        sph.setColor(new Color(255,0,0));
        polygons.add(sph);
        
        
        sph = new Sphere(new Point(-30,30,0,1),5);
        sph.setColor(new Color(0,0,255));
        polygons.add(sph);
        
        sph = new Sphere(new Point(-30,-30,0,1),5);
        sph.setColor(new Color(255,0,255));
        polygons.add(sph);
        
        sph = new Sphere(new Point(30,-30,0,1),5);
        sph.setColor(new Color(255,165,0));
        polygons.add(sph);
        
        sph = new Sphere(new Point(0,0,-45,1),5);
        sph.setColor(new Color(0,255,0));
        polygons.add(sph);
        
        sph = new Sphere(new Point(0,0,45,1),5);
        sph.setColor(new Color(0,255,255));
        polygons.add(sph);
        
        Polygon poly = new Polygon();
        
        Point p = new Point(-1000,-100,-1000,1);
        p.setNormal(new Point(0,1,0,1));
        poly.addPointToPolygon(p);
        
        p = new Point(1000,-100,-1000,1);
        p.setNormal(new Point(0,1,0,1));
        poly.addPointToPolygon(p);
        
        p = new Point(1000,-100,1000,1);
        p.setNormal(new Point(0,1,0,1));
        poly.addPointToPolygon(p);
        
        p = new Point(-1000,-100,1000,1);
        p.setNormal(new Point(0,1,0,1));
        poly.addPointToPolygon(p);
        
        poly.setColor(new Color(0,210,165));
        poly.setNormal(new Point(0,1,0,1));
        poly.setDistanceOrigin(100f);
        polygons.add(poly);
        
        
        /*
        //Bottom
        Polygon square1 = new Polygon();
        p = new Point(-20,-20,-20,1);
        p.setNormal(new Point(0,-1,0,1));
        square1.addPointToPolygon(p);
        
        p = new Point(20,-20,-20,1);
        p.setNormal(new Point(0,-1,0,1));
        square1.addPointToPolygon(p);
        
        p = new Point(20,-20,20,1);
        p.setNormal(new Point(0,-1,0,1));
        square1.addPointToPolygon(p);
        
        p = new Point(-20,-20,20,1);
        p.setNormal(new Point(0,-1,0,1));
        square1.addPointToPolygon(p);
        
        square1.setColor(new Color(1f,1f,1f));
        square1.setNormal(new Point(0,1,0,1));
        square1.setDistanceOrigin(20);
        polygons.add(square1);
        
       //Top
         Polygon square2 = new Polygon();
        p = new Point(-20,20,-20,1);
        p.setNormal(new Point(0,1,0,1));
        square2.addPointToPolygon(p);
        
        p = new Point(20,20,-20,1);
        p.setNormal(new Point(0,1,0,1));
        square2.addPointToPolygon(p);
        
        p = new Point(20,20,20,1);
        p.setNormal(new Point(0,-1,0,1));
        square2.addPointToPolygon(p);
        
        p = new Point(-20,20,20,1);
        p.setNormal(new Point(0,-1,0,1));
        square2.addPointToPolygon(p);
        
        square2.setColor(new Color(1f,1f,1f));
        square2.setNormal(new Point(0,-1,0,1));
        square2.setDistanceOrigin(20);
        polygons.add(square2);
        
        //Left
         Polygon square3 = new Polygon();
        p = new Point(-20,20,-20,1);
        p.setNormal(new Point(-1,0,0,1));
        square3.addPointToPolygon(p);
        
        p = new Point(-20,20,20,1);
        p.setNormal(new Point(-1,0,0,1));
        square3.addPointToPolygon(p);
        
        p = new Point(-20,-20,20,1);
        p.setNormal(new Point(-1,0,0,1));
        square3.addPointToPolygon(p);
        
        p = new Point(-20,-20,-20,1);
        p.setNormal(new Point(-1,0,0,1));
        square3.addPointToPolygon(p);
        
        square3.setColor(new Color(1f,1f,1f));
        square3.setNormal(new Point(1,0,0,1));
        square3.setDistanceOrigin(20);
        polygons.add(square3);
        
         //Right
         Polygon square4 = new Polygon();
        p = new Point(20,20,-20,1);
        p.setNormal(new Point(1,0,0,1));
        square4.addPointToPolygon(p);
        
        p = new Point(20,20,20,1);
        p.setNormal(new Point(1,0,0,1));
        square4.addPointToPolygon(p);
        
        p = new Point(20,-20,20,1);
        p.setNormal(new Point(1,0,0,1));
        square4.addPointToPolygon(p);
        
        p = new Point(20,-20,-20,1);
        p.setNormal(new Point(1,0,0,1));
        square4.addPointToPolygon(p);
        
        square4.setColor(new Color(1f,1f,1f));
        square4.setNormal(new Point(-1,0,0,1));
        square4.setDistanceOrigin(20);
        polygons.add(square4);
        
        //Back
         Polygon square5 = new Polygon();
        p = new Point(-20,20,-20,1);
        p.setNormal(new Point(0,0,-1,1));
        square5.addPointToPolygon(p);
        
        p = new Point(20,20,-20,1);
        p.setNormal(new Point(0,0,-1,1));
        square5.addPointToPolygon(p);
        
        p = new Point(20,-20,-20,1);
        p.setNormal(new Point(0,0,-1,1));
        square5.addPointToPolygon(p);
        
        p = new Point(-20,-20,-20,1);
        p.setNormal(new Point(0,0,-1,1));
        square5.addPointToPolygon(p);
        
        square5.setColor(new Color(1f,1f,1f));
        square5.setNormal(new Point(0,0,1,1));
        square5.setDistanceOrigin(20);
        polygons.add(square5);
        
        //Front
         Polygon square6 = new Polygon();
        p = new Point(-20,20,20,1);
        p.setNormal(new Point(0,0,1,1));
        square6.addPointToPolygon(p);
        
        p = new Point(20,20,20,1);
        p.setNormal(new Point(0,0,1,1));
        square6.addPointToPolygon(p);
        
        p = new Point(20,-20,20,1);
        p.setNormal(new Point(0,0,1,1));
        square6.addPointToPolygon(p);
        
        p = new Point(-20,-20,20,1);
        p.setNormal(new Point(0,0,1,1));
        square6.addPointToPolygon(p);
        
        square6.setColor(new Color(1f,1f,1f));
        square6.setNormal(new Point(0,0,-1,1));
        square6.setDistanceOrigin(20);
        polygons.add(square6);*/
        
    }
    
    public void loadData(String fileName)
    {
        polygons = new ArrayList();

        File file = new File(fileName);
        BufferedReader reader = null;

        try
        {
            reader = new BufferedReader(new FileReader(file));
            String text = null;

            Polygon poly = null;
            int groups = 0;
            int polyCount = 0;
            int vertices = 0;

            float hi = -1000000;
            float low = 1000000;

            //Get C
            text = reader.readLine().trim();
            //System.out.println(text);

            if (!text.equals("c"))
            {
                throw new Exception("Bad file format");
            }

            //Group Numbers
            text = reader.readLine().trim();
            //System.out.println(text);

            groups = Integer.parseInt(text);

            for (int i = 0; i < groups; i++)
            {
                //number of polygons in group
                text = reader.readLine().trim();
                //System.out.println(text);

                polyCount = Integer.parseInt(text);

                List<Polygon> group = new ArrayList();

                for (int x = 0; x < polyCount; x++)
                {
                    poly = new Polygon();
                    text = reader.readLine().trim();
                    //System.out.println(text);

                    vertices = Integer.parseInt(text);

                    for (int y = 0; y < vertices; y++)
                    {
                        text = reader.readLine().trim();
                       System.out.println(text);

                        String[] point = text.split(" ");
                       
                        Point p = new Point(Float.parseFloat(point[0]), Float.parseFloat(point[1]), Float.parseFloat(point[2]), 1);
                        Point n;
                        if(point.length > 3)
                        {
                            n = new Point(Float.parseFloat(point[3]), Float.parseFloat(point[4]), Float.parseFloat(point[5]), 1);
                        }
                        else
                        {
                            n = new Point(0,0,0,1);
                        }
                        n.normalize();
                        p.setNormal(n);
                        if (p.getX() > hi)
                        {
                            hi = p.getX();
                        }
                        if (p.getX() < low)
                        {
                            low = p.getX();
                        }
                        if (p.getY() > hi)
                        {
                            hi = p.getY();
                        }
                        if (p.getY() < low)
                        {
                            low = p.getY();
                        }
                        if (p.getZ() > hi)
                        {
                            hi = p.getZ();
                        }
                        if (p.getZ() < low)
                        {
                            low = p.getZ();
                        }

                        poly.addPointToPolygon(p);
                    }

                    group.add(poly);
                }

                //color
                text = reader.readLine().trim();
                //System.out.println(text);
                String color[] = text.split(" ");
                Color c = new Color(Float.parseFloat(color[0]), Float.parseFloat(color[1]), Float.parseFloat(color[2]));

                for (Polygon pp : group)
                {
                    pp.setColor(c);
                }

                polygons.addAll(group);

            }

            center = new Point(0, 0, 0, 1);
            
            CS455Matrix m = scaleWorld(low, hi);
            for (Shape p : polygons)
            {
                
                p.multiplyMatrix(m);
                
            }
           
            

            if (reader != null)
            {
                reader.close();
            }
        } catch (FileNotFoundException e)
        {
            e.printStackTrace();
        } catch (IOException e)
        {
            e.printStackTrace();
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}